package p.ithorns.integration.zk.id;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

/**
 * SampleIdGenerator
 * TODO
 *
 * @author ithorns
 * @version 1.0
 * @date 2021/9/8 18:32
 */
@Slf4j
public class SampleIdGenerator {

    private final AtomicInteger index = new AtomicInteger(0);
    private final AtomicLong lastTimeMillis = new AtomicLong();

    // IDG ID
    private final int workerIdIndex;

    private static final int workIdLen = 2;

    private static final int seedLen = 3;

    // 1毫秒内能生成的ID数
    private static final int seed = (int) Math.pow(IdFlatter.DIGITS.length, seedLen);

    /**
     * 时间以 2019-01-01 0：0：0 作为起始。而不是从 1970-1-1 减少时间代表的长度。
     */
    private static final long startTime = 1546272000000L;

    public static SampleIdGenerator getSingle() {
        return single;
    }

    public static void setSingle(SampleIdGenerator idGenerator) {
        single = idGenerator;
    }

    @PostConstruct
    public void setSingle() {
        setSingle(this);
    }

    private static SampleIdGenerator single;

    public SampleIdGenerator() {
        this.workerIdIndex = 7;
    }

    /**
     * 获取当前相对 2019-05-01 的时间 毫秒数
     *
     * @return
     */
    private static long getRelativeNowTime() {
        return System.currentTimeMillis() - startTime;
    }

    public static NumId toNumId(String idStr) {
        return NumId.byIdStr(idStr);
    }

    @Data
    public static final class NumId {
        private long relativeTime = 0L;
        private int seed = 0;
        private int workId = 0;

        public long getTime() {
            return relativeTime + startTime;
        }

        public long getWeekRelativeTime() {
            return relativeTime % 604800000;
        }

        @Override
        public String toString() {
            return toNumStr();
        }

        public String toNumStr() {
            StringBuilder sb = new StringBuilder(32).append(getTime());
            String str = String.valueOf(seed);
            for (int i = 5 - str.length(); i > 0; i--) {
                sb.append('0');
            }
            sb.append(str);
            str = String.valueOf(workId);
            for (int i = 4 - str.length(); i > 0; i--) {
                sb.append('0');
            }
            sb.append(str);
            return sb.toString();
        }

        public final String toIdStr() {
            return IdFlatter.toStr(relativeTime) +
                    IdFlatter.toFixedLStr(seed, seedLen) +
                    IdFlatter.toFixedLStr(workId, 2);
        }

        public static NumId byNumIdStr(String numIdStr) {
            if (numIdStr.length() <= 9)
                throw new IllegalArgumentException(" numIdStr  is error ");
            NumId id = new NumId();
            id.setWorkId(Integer.valueOf(numIdStr.substring(numIdStr.length() - 4)));
            id.setSeed(Integer.valueOf(numIdStr.substring(numIdStr.length() - 9, numIdStr.length() - 4)));
            id.setRelativeTime(Long.valueOf(numIdStr.substring(0, numIdStr.length() - 9)) - startTime);
            if (id.getRelativeTime() < 0)
                throw new IllegalArgumentException(" numIdStr  is error ");
            return id;
        }

        public static NumId byIdStr(String idStr) {
            return new NumId(idStr);
        }


        public NumId() {
        }

        public NumId(String idStr) {
            if (!StringUtils.hasText(idStr)) {
                throw new IllegalArgumentException("id is empty");
            }
            char[] cs = idStr.toCharArray();
            int i, temp = 0, len = cs.length - (seedLen + workIdLen);
            char c = 0;
            while (temp < len) {
                i = (c = cs[temp]) - 'a';
                if (i < 0) {
                    i = c - '0';
                } else
                    i += 10;
                relativeTime = relativeTime * 32 + i;
                temp++;
            }
            len = cs.length - workIdLen;
            while (temp < len) {
                i = (c = cs[temp]) - 'a';
                if (i < 0) {
                    i = c - '0';
                } else
                    i += 10;
                seed = seed * 32 + i;
                temp++;
            }
            len = cs.length;
            while (temp < cs.length) {
                i = (c = cs[temp]) - 'a';
                if (i < 0) {
                    i = c - '0';
                } else
                    i += 10;
                workId = workId * 32 + i;
                temp++;
            }
        }
    }

    /**
     * 获得ID
     */
    public final NumId nextNumId() {
        NumId id = new NumId();
        id.setWorkId(workerIdIndex);
        long t = getRelativeNowTime(), t2;
        for (; t > (t2 = lastTimeMillis.get()); ) {
            if (lastTimeMillis.compareAndSet(t2, t)) {
                index.set(0);
                break;
            }
        }
        if (t < t2) {
            // 该情况出现在系统时钟回拨。此情况出现，则代表ID极可能重复。
            log.error("时钟回拨");
        }

        id.setRelativeTime(t);
        index.compareAndSet(seed, 0);
        id.setSeed(index.incrementAndGet());
        return id;
    }

    /**
     * 获得ID
     */
    public final String nextId() {
        return nextNumId().toIdStr();
    }

//    public static void main(String[] args) {
//        SampleIdGenerator g = new SampleIdGenerator();
//
//        String str = g.nextId();
//        System.out.println(str);
//        NumId i = toNumId(str);
//        System.out.println(i);
//        System.out.println(i.toString().length());
//        System.out.println(i.getTime());
//        System.out.println(i.getWeekRelativeTime());
//        System.out.println(i.toIdStr());
//        System.out.println(NumId.byNumIdStr(i.toString()).toIdStr());
//    }
}
